package org.infinispan.persistence.sifs;
import static org.testng.Assert.fail;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.infinispan.commons.util.Util;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.container.InternalEntryFactory;
import org.infinispan.container.InternalEntryFactoryImpl;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.filter.KeyFilter;
import org.infinispan.marshall.TestObjectStreamMarshaller;
import org.infinispan.marshall.core.MarshalledEntry;
import org.infinispan.persistence.sifs.configuration.SoftIndexFileStoreConfigurationBuilder;
import org.infinispan.test.AbstractInfinispanTest;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.infinispan.test.fwk.TestInternalCacheEntryFactory;
import org.infinispan.util.DefaultTimeService;
import org.infinispan.util.PersistenceMockUtil;
import org.infinispan.util.concurrent.WithinThreadExecutor;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* @author Radim Vansa <rvansa@redhat.com>
*/
@Test(groups = "stress", testName = "persistence.SoftIndexFileStoreStressTest", timeOut = 15*60*1000)
public class SoftIndexFileStoreStressTest extends AbstractInfinispanTest {
protected static final int THREADS = 10;
protected static final long TEST_DURATION = TimeUnit.MINUTES.toMillis(10);
protected static final int KEY_RANGE = 1000;
private TestObjectStreamMarshaller marshaller;
private InternalEntryFactory factory;
private SoftIndexFileStore store;
private String tmpDirectory;
private ExecutorService executorService;
private volatile boolean terminate;
private DefaultTimeService timeService;
@BeforeMethod(alwaysRun = true)
public void setUp() throws Exception {
tmpDirectory = TestingUtil.tmpDirectory(this.getClass());
Util.recursiveFileRemove(tmpDirectory);
marshaller = new TestObjectStreamMarshaller();
factory = new InternalEntryFactoryImpl();
store = new SoftIndexFileStore();
ConfigurationBuilder builder = TestCacheManagerFactory
.getDefaultCacheConfiguration(false);
log.info("Using directory " + tmpDirectory);
builder.persistence()
.addStore(SoftIndexFileStoreConfigurationBuilder.class)
.indexLocation(tmpDirectory).dataLocation(tmpDirectory + "/data")
.purgeOnStartup(false)
.maxFileSize(1000);
timeService = new DefaultTimeService();
store.init(PersistenceMockUtil.createContext(getClass().getSimpleName(), builder.build(), marshaller, timeService));
((InternalEntryFactoryImpl) factory).injectTimeService(timeService);
store.start();
executorService = Executors.newFixedThreadPool(THREADS + 1);
}
@AfterMethod
public void shutdown() {
store.clear();
store.stop();
marshaller.stop();
executorService.shutdown();
}
public void test() throws ExecutionException, InterruptedException {
terminate = false;
ArrayList<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < THREADS; ++i) {
Future<?> future = executorService.submit(new TestThread());
futures.add(future);
}
executorService.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
Thread.sleep(TEST_DURATION);
terminate = true;
return null;
}
});
for (Future<?> future : futures) {
future.get();
}
// let's wait so that we don't store expired values in the map
Thread.sleep(100);
Map<Object, Object> entries = new HashMap<>();
store.process(KeyFilter.ACCEPT_ALL_FILTER, (marshalledEntry, taskContext) -> {
Object prev = entries.put(marshalledEntry.getKey(), marshalledEntry.getValue());
if (prev != null) {
fail("Returned entry twice: " + marshalledEntry.getKey() + " -> " + prev + ", " + marshalledEntry.getValue());
}
}, new WithinThreadExecutor(), true, false);
store.stop();
// remove index files
Stream.of(new File(tmpDirectory).listFiles(file -> !file.isDirectory())).map(f -> f.delete());
store.start();
store.process(KeyFilter.ACCEPT_ALL_FILTER, (marshalledEntry, taskContext) -> {
Object stored = entries.get(marshalledEntry.getKey());
if (stored == null) {
fail("Loaded " + marshalledEntry.getKey() + " -> " + marshalledEntry.getValue() + " but it's not in the map");
} else if (!Objects.equals(stored, marshalledEntry.getValue())) {
fail("Loaded " + marshalledEntry.getKey() + " -> " + marshalledEntry.getValue() + " but it's was " + stored);
}
}, new WithinThreadExecutor(), true, false);
for (Map.Entry<Object, Object> entry : entries.entrySet()) {
MarshalledEntry loaded = store.load(entry.getKey());
if (loaded == null) {
fail("Did not load " + entry.getKey() + " -> " + entry.getValue());
} else if (!Objects.equals(entry.getValue(), loaded.getValue())) {
fail("Loaded " + entry.getKey() + " -> " + loaded.getValue() + " but it should be " + entry.getValue());
}
}
}
private class TestThread implements Runnable {
@Override
public void run() {
ThreadLocalRandom random = ThreadLocalRandom.current();
InternalCacheEntry ice;
while (!terminate) {
long lifespan;
String key = key(random);
switch (random.nextInt(3)) {
case 0:
lifespan = random.nextInt(3) == 0 ? random.nextInt(10) : -1;
ice = TestInternalCacheEntryFactory.<Object, Object>create(factory,
key(random), String.valueOf(random.nextInt()), lifespan);
store.write(TestingUtil.marshalledEntry(ice, marshaller));
break;
case 1:
store.delete(key);
break;
case 2:
store.load(key);
}
}
}
}
protected String key(ThreadLocalRandom random) {
return "key" + random.nextInt(KEY_RANGE);
}
}